Simply knowing the nuts and bolts of working
code doesn't mean that we're ready to architect maintainable, reusable,
service-oriented applications. We need to become intimately familiar
with standard patterns and always keep key principles in mind in order
to truly build long-lasting SOA solutions.
The core principles of a service-oriented architecture
So what exactly is a service? A service
is essentially a well-defined interface to an autonomous chunk of
functionality, which usually corresponds to a specific business
process. That might sounds a lot like a regular old object-oriented
component to you. While both services and components have commonality
in that they expose discrete interfaces of functionality, a service is
more focused on the capabilities offered than the packaging. Services
are meant to be higher-level, business-oriented offerings that provide
technology abstraction and interoperability within a multipurpose
"services" tier of your architecture.
What makes up a service? Typically you'll find:
Contract:
Explains what operations the service exposes, types of messages, and
exchange patterns supported by this service, and any policies that
explain how this service is used.
Messages: The data payload exchanged between the service consumer and provider.
Implementation:
The portion of the service which actually processes the requests,
executes the expected business functionality, and optionally returns a
response.
Service provider: The host of the service which publishes the interface and manages the lifetime of the service.
Service consumer:
Ideally, a service has someone using it. The service consumer is aware
of the available service operations and knows how to discover the
provider and determine what type of messages to transmit.
Facade:
Optionally, a targeted facade may be offered to particularly service
consumers. This sort of interface may offer a more simplified
perspective on the service, or provide a coarse-grained avenue for
service invocation.
What
is the point of building a service? I'd say it's to construct an asset
capable of being reused which means that it's a discrete, discoverable,
self-describing entity that can be accessed regardless of platform or
technology.
Service-oriented architecture is defined as an
architectural discipline based on loosely-coupled, autonomous chunks of
business functionality which can be used to construct composite
applications. Through the rest of this article we get a chance to flesh out many of the concepts that underlie
that statement. Let's go ahead and take a look at a few of the
principles and characteristics that I consider most important to a
successful service-oriented BizTalk solution. As part of each one, I'll
explain the thinking behind the principle and then call out how it can
be applied to BizTalk Server solutions.
Loosely coupled
Many
of the fundamental SOA principles actually stem from this particular
one. In virtually all cases, some form of coupling between components
is inevitable. The only way we can effectively build software is to
have interrelations between the various components that make up the
delivered product. However, when architecting solutions, we have
distinct design decisions to make regarding the extent to which
application components are coupled. Loose coupling is all about
establishing relationships with minimal dependencies.
What
would a tightly-coupled application look like? In such an application,
we'd find components that maintained intimate knowledge of each others'
working parts and engaged in frequent, chatty synchronous calls amongst
themselves. Many components in the application would retain state and
allow consumers to manipulate that state data. Transactions that take
place in a tightly coupled application probably adhere to a two-phase commit
strategy where all components must succeed together in order for each
data interaction to be finalized. The complete solution has its
ensemble of components compiled together and singularly deployed to one
technology platform. In order to run properly, these tightly-coupled
components rely on the full availability of each component to fulfill
the requests made of them.
On
the other hand, a loosely-coupled application employs a wildly
different set of characteristics. Components in this sort of
application share only a contract and keep their implementation details
hidden. Rarely preserving state data, these components rely on less
frequent communication where chunky input containing all the data the
component needs to satisfy its requestors is shared. Any transactions
in these types of applications often follow a compensation
strategy where we don't assume that all components can or will commit
their changes at the same time. This class of solution can be
incrementally deployed to a mix of host technologies. Asynchronous
communication between components, often through a broker, enables a
less stringent operational dependency between the components that
comprise the solution.
What
makes a solution loosely coupled then? Notably, the primary information
shared by a component is its interface. The consuming component
possesses no knowledge of the internal implementation details. The
contract relationship suffices as a means of explaining how the target
component is used. Another trait of loosely coupled solutions is
coarse-grained interfaces that encourage the transmission of full data
entities as opposed to fine-grained interfaces, which accept small
subsets of data. Because loosely-coupled components do not share state
information, a thicker input message containing a complete impression
of the entity is best. Loosely-coupled applications also welcome the
addition of a broker which proxies the (often asynchronous)
communication between components. This mediator permits a rich
decoupling where runtime binding between components can be dynamic and
components can forgo an operational dependency on each other.
Let's take a look at an example of loose coupling that sits utterly outside the realm of technology.
Completely non-technical loose coupling example
When
I go to a restaurant and place an order with my waiter, he captures the
request on his pad and sends that request to the kitchen. The order pad
(the contract) contains all the data needed by the kitchen chef to
create my meal. The restaurant owner can bring in a new waiter or
rotate his chefs and the restaurant shouldn't skip a beat as both roles
(services) serve distinct functions where the written order is the
intersection point and highlight of their relationship.
Why
does loose coupling matter? By designing a loosely-coupled solution,
you provide a level of protection against the changes that the
application will inevitably require over its life span. We have to
reduce the impact of such changes while making it possible to deploy
necessary updates in an efficient manner.
How does this apply to BizTalk Server solutions?
A
good portion of the BizTalk Server architecture was built with loose
coupling in mind. Think about the BizTalk MessageBox which acts as a
broker facilitating communication between ports and orchestrations
while limiting any tight coupling. Receive ports and send ports are
very loosely coupled and in many cases, have absolutely no awareness of
each other. The publish-and-subscribe bus thrives on the asynchronous
transfer of self-describing messages between stateless endpoints. Let's
look at a few recommendations of how to build loosely-coupled BizTalk
applications.
Orchestrations
are a prime place where you can either go with a tightly-coupled or
loosely-coupled design route. For instance, when sketching out your
orchestration process, it's sure tempting to use that Transform
shape to convert from one message type to another. However, a version
change to that map will require a modification of the calling
orchestration. When mapping to or from data structures associated with
external systems, it's wiser to push those maps to the edges
(receive/send ports) and not embed a direct link to the map within the
orchestration.
BizTalk
easily generates schemas for line-of-business (LOB) systems and
consumed services. To interact with these schemas in a very loosely
coupled fashion, consider defining stable entity schemas (i.e.
"canonical schemas") that are used within an orchestration, and only
map to the format of the LOB system in the send port. For example, if
you need to send a piece of data into an Oracle database table, you can
certainly include a map within an orchestration which instantiates the
Oracle message. However, this will create a tight coupling between the
orchestration and the database structure. To better insulate against
future changes to the database schema, consider using a generic
intermediate data format in the orchestration and only transforming to
the Oracle-specific format in the send port.
How
about those logical ports that we add to orchestrations to facilitate
the transfer of messages in and out of the workflow process? When
configuring those ports, the Port Configuration Wizard asks you if you want to associate the port to a physical endpoint via the Specify Now
option. Once again, pretty tempting. If you know that the message will
arrive at an orchestration via a FILE adapter, why not just go ahead
and configure that now and let Visual Studio.NET create the
corresponding physical ports during deployment? While you can
independently control the auto-generated physical ports later on, it's
a bad idea to embed transport details inside the orchestration file.